#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
图片裁减软件 - 批量调整图片尺寸和大小
功能：
1. 按宽度调整图片尺寸（高度自动，保持宽高比）
2. 按文件大小限制图片大小（自动计算合适尺寸）
3. 批量处理文件夹中的图片
4. 支持主流图片格式
"""

import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import os
import math
import shutil  # 添加文件复制功能
import tempfile
from pathlib import Path
from PIL import Image, ImageTk
import threading
from typing import List, Tuple, Optional


class ImageProcessor:
    """图片处理核心类"""
    
    # 支持的图片格式
    SUPPORTED_FORMATS = {'.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif', '.webp'}
    
    def __init__(self):
        self.stop_processing = False
    
    def copy_skipped_file(self, input_path: str, output_path: str) -> bool:
        """
        复制跳过处理的文件到输出目录
        
        Args:
            input_path: 源文件路径
            output_path: 目标文件路径
            
        Returns:
            bool: 复制是否成功
        """
        try:
            # 如果输出文件已存在，直接替换
            if os.path.exists(output_path):
                print(f"替换已存在的文件: {os.path.basename(output_path)}")
            
            # 复制文件
            shutil.copy2(input_path, output_path)
            return True
            
        except Exception as e:
            print(f"复制文件 {input_path} 时出错: {e}")
            return False
    
    def resize_by_width(self, image_path: str, target_width: int, output_path: str) -> bool:
        """
        按指定宽度调整图片尺寸，保持宽高比
        如果原图宽度小于等于目标宽度，则不处理
        
        Args:
            image_path: 原图片路径
            target_width: 目标宽度（像素）
            output_path: 输出路径
            
        Returns:
            bool: 处理是否成功，如果跳过处理返回True
        """
        try:
            with Image.open(image_path) as img:
                # 获取原始尺寸
                original_width, original_height = img.size
                
                # 如果原图宽度小于等于目标宽度，则复制原文件
                if original_width <= target_width:
                    print(f"跳过图片 {os.path.basename(image_path)}：原宽度({original_width})小于等于目标宽度({target_width})，复制原文件")
                    return self.copy_skipped_file(image_path, output_path)
                
                # 计算新高度，保持宽高比
                aspect_ratio = original_height / original_width
                target_height = int(target_width * aspect_ratio)
                
                # 调整尺寸
                resized_img = img.resize((target_width, target_height), Image.Resampling.LANCZOS)
                
                # 如果输出文件已存在，直接替换
                if os.path.exists(output_path):
                    print(f"替换已存在的文件: {os.path.basename(output_path)}")
                
                # 保存图片
                resized_img.save(output_path, optimize=True, quality=95)
                return True
                
        except Exception as e:
            print(f"处理图片 {image_path} 时出错: {e}")
            return False
    
    def resize_by_file_size(self, image_path: str, target_size_mb: float, output_path: str) -> bool:
        """
        按指定文件大小调整图片
        如果原图文件大小小于等于目标大小，则不处理
        
        Args:
            image_path: 原图片路径
            target_size_mb: 目标文件大小（MB）
            output_path: 输出路径
            
        Returns:
            bool: 处理是否成功，如果跳过处理返回True
        """
        try:
            # 检查原文件大小
            original_size_bytes = os.path.getsize(image_path)
            original_size_mb = original_size_bytes / (1024 * 1024)
            target_size_bytes = target_size_mb * 1024 * 1024
            
            # 如果原文件小于等于目标大小，则复制原文件
            if original_size_bytes <= target_size_bytes:
                print(f"跳过图片 {os.path.basename(image_path)}：原大小({original_size_mb:.2f}MB)小于等于目标大小({target_size_mb}MB)，复制原文件")
                return self.copy_skipped_file(image_path, output_path)
            
            with Image.open(image_path) as img:
                original_width, original_height = img.size
                
                # 估算初始质量和尺寸
                quality = 95
                scale_factor = 1.0
                
                # 二分法查找合适的参数
                for attempt in range(10):  # 最多尝试10次
                    # 计算新尺寸
                    new_width = int(original_width * scale_factor)
                    new_height = int(original_height * scale_factor)
                    
                    # 调整图片尺寸
                    resized_img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
                    
                    # 保存到临时路径测试文件大小
                    # 获取原图片的扩展名
                    original_ext = Path(image_path).suffix.lower()
                    if original_ext not in self.SUPPORTED_FORMATS:
                        original_ext = '.jpg'  # 默认使用jpg
                    
                    temp_fd, temp_path = tempfile.mkstemp(suffix=original_ext)
                    os.close(temp_fd)  # 关闭文件描述符
                    resized_img.save(temp_path, optimize=True, quality=quality)
                    
                    # 检查文件大小
                    current_size = os.path.getsize(temp_path)
                    
                    if current_size <= target_size_bytes:
                        # 文件大小符合要求，使用这个版本
                        # 如果输出文件已存在，直接替换
                        if os.path.exists(output_path):
                            print(f"替换已存在的文件: {os.path.basename(output_path)}")
                        
                        os.rename(temp_path, output_path)
                        return True
                    else:
                        # 文件太大，减小尺寸或质量
                        os.remove(temp_path)
                        if quality > 80:  # 质量最低限制为80
                            quality -= 10  # 降低质量
                        else:
                            scale_factor *= 0.9  # 缩小尺寸
                
                # 如果还是太大，最后一次尝试用最低设置
                new_width = int(original_width * scale_factor)
                new_height = int(original_height * scale_factor)
                resized_img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
                
                # 如果输出文件已存在，直接替换
                if os.path.exists(output_path):
                    print(f"替换已存在的文件: {os.path.basename(output_path)}")
                
                resized_img.save(output_path, optimize=True, quality=80)  # 最低质量设为80
                return True
                
        except Exception as e:
            print(f"处理图片 {image_path} 时出错: {e}")
            return False
    
    def get_image_files(self, folder_path: str, exclude_path: Optional[str] = None) -> List[str]:
        """
        获取文件夹中所有支持的图片文件
        
        Args:
            folder_path: 文件夹路径
            exclude_path: 需要排除的路径（通常是输出目录）
            
        Returns:
            List[str]: 图片文件路径列表
        """
        image_files = []
        try:
            folder = Path(folder_path)
            exclude_folder = Path(exclude_path) if exclude_path else None
            
            for file_path in folder.rglob('*'):
                if file_path.is_file() and file_path.suffix.lower() in self.SUPPORTED_FORMATS:
                    # 检查是否在排除路径中
                    if exclude_folder:
                        try:
                            # 尝试获取相对路径，如果成功说明是子路径
                            file_path.relative_to(exclude_folder)
                            continue  # 跳过输出目录中的文件
                        except ValueError:
                            # 不是子路径，继续处理
                            pass
                    
                    image_files.append(str(file_path))
        except Exception as e:
            print(f"扫描文件夹时出错: {e}")
        
        return image_files


class ImageResizerGUI:
    """图片裁减软件GUI界面"""
    
    def __init__(self, root):
        self.root = root
        self.root.title("图片裁减软件 v1.7")
        self.root.geometry("600x550")  # 增加高度以容纳版权信息
        self.root.resizable(True, True)
        
        # 窗体居中显示
        self.center_window()
        
        # 初始化图片处理器
        self.processor = ImageProcessor()
        
        # 初始化变量
        self.source_folder = tk.StringVar()
        self.output_folder = tk.StringVar()
        self.resize_mode = tk.StringVar(value="width")  # width 或 filesize
        self.target_width = tk.StringVar(value="800")
        self.target_size = tk.StringVar(value="2.0")
        
        # 创建界面
        self.create_widgets()
        
        # 处理状态
        self.is_processing = False
    
    def center_window(self):
        """将窗口居中显示"""
        self.root.update_idletasks()  # 确保窗口尺寸已更新
        
        # 获取窗口尺寸
        window_width = self.root.winfo_width()
        window_height = self.root.winfo_height()
        
        # 获取屏幕尺寸
        screen_width = self.root.winfo_screenwidth()
        screen_height = self.root.winfo_screenheight()
        
        # 计算居中位置
        x = (screen_width - window_width) // 2
        y = (screen_height - window_height) // 2
        
        # 设置窗口位置
        self.root.geometry(f"{window_width}x{window_height}+{x}+{y}")
    
    def create_widgets(self):
        """创建GUI组件"""
        # 主框架
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # 配置网格权重
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)
        main_frame.columnconfigure(1, weight=1)
        
        # 标题
        title_label = ttk.Label(main_frame, text="图片裁减软件", font=("Arial", 16, "bold"))
        title_label.grid(row=0, column=0, columnspan=3, pady=(0, 20))
        
        # 文件夹选择区域
        folder_frame = ttk.LabelFrame(main_frame, text="文件夹选择", padding="5")
        folder_frame.grid(row=1, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 10))
        folder_frame.columnconfigure(1, weight=1)
        
        # 源文件夹
        ttk.Label(folder_frame, text="源文件夹:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))
        ttk.Entry(folder_frame, textvariable=self.source_folder).grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(0, 5))
        ttk.Button(folder_frame, text="浏览", command=self.browse_source_folder).grid(row=0, column=2)
        
        # 输出文件夹
        ttk.Label(folder_frame, text="输出文件夹:").grid(row=1, column=0, sticky=tk.W, padx=(0, 5), pady=(5, 0))
        ttk.Entry(folder_frame, textvariable=self.output_folder).grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(0, 5), pady=(5, 0))
        ttk.Button(folder_frame, text="浏览", command=self.browse_output_folder).grid(row=1, column=2, pady=(5, 0))
        
        # 处理模式选择
        mode_frame = ttk.LabelFrame(main_frame, text="处理模式", padding="5")
        mode_frame.grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 10))
        
        # 按宽度调整
        width_radio = ttk.Radiobutton(mode_frame, text="按宽度调整尺寸", variable=self.resize_mode, value="width")
        width_radio.grid(row=0, column=0, sticky=tk.W)
        
        width_frame = ttk.Frame(mode_frame)
        width_frame.grid(row=1, column=0, sticky=(tk.W, tk.E), padx=(20, 0))
        
        ttk.Label(width_frame, text="目标宽度:").grid(row=0, column=0, sticky=tk.W)
        width_entry = ttk.Entry(width_frame, textvariable=self.target_width, width=10)
        width_entry.grid(row=0, column=1, padx=(5, 5))
        ttk.Label(width_frame, text="像素 (高度自动调整)").grid(row=0, column=2, sticky=tk.W)
        
        # 按文件大小调整
        size_radio = ttk.Radiobutton(mode_frame, text="按文件大小限制", variable=self.resize_mode, value="filesize")
        size_radio.grid(row=2, column=0, sticky=tk.W, pady=(10, 0))
        
        size_frame = ttk.Frame(mode_frame)
        size_frame.grid(row=3, column=0, sticky=(tk.W, tk.E), padx=(20, 0))
        
        ttk.Label(size_frame, text="目标大小:").grid(row=0, column=0, sticky=tk.W)
        size_entry = ttk.Entry(size_frame, textvariable=self.target_size, width=10)
        size_entry.grid(row=0, column=1, padx=(5, 5))
        ttk.Label(size_frame, text="MB (自动计算合适尺寸)").grid(row=0, column=2, sticky=tk.W)
        
        # 进度显示区域
        progress_frame = ttk.LabelFrame(main_frame, text="处理进度", padding="5")
        progress_frame.grid(row=3, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 10))
        progress_frame.columnconfigure(0, weight=1)
        
        # 进度条
        self.progress_var = tk.DoubleVar()
        self.progress_bar = ttk.Progressbar(progress_frame, variable=self.progress_var, maximum=100)
        self.progress_bar.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 5))
        
        # 状态标签
        self.status_label = ttk.Label(progress_frame, text="就绪")
        self.status_label.grid(row=1, column=0, sticky=tk.W)
        
        # 控制按钮
        button_frame = ttk.Frame(main_frame)
        button_frame.grid(row=4, column=0, columnspan=3, pady=(0, 10))
        
        self.start_button = ttk.Button(button_frame, text="开始处理", command=self.start_processing)
        self.start_button.pack(side=tk.LEFT, padx=(0, 10))
        
        self.stop_button = ttk.Button(button_frame, text="停止处理", command=self.stop_processing, state="disabled")
        self.stop_button.pack(side=tk.LEFT)
        
        # 日志显示区域
        log_frame = ttk.LabelFrame(main_frame, text="处理日志", padding="5")
        log_frame.grid(row=5, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
        log_frame.columnconfigure(0, weight=1)
        log_frame.rowconfigure(0, weight=1)
        main_frame.rowconfigure(5, weight=1)
        
        # 文本框和滚动条
        self.log_text = tk.Text(log_frame, height=8, wrap=tk.WORD)
        scrollbar = ttk.Scrollbar(log_frame, orient="vertical", command=self.log_text.yview)
        self.log_text.configure(yscrollcommand=scrollbar.set)
        
        self.log_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
        
        # 版权信息
        copyright_frame = ttk.Frame(main_frame)
        copyright_frame.grid(row=6, column=0, columnspan=3, pady=(10, 0))
        
        copyright_label = ttk.Label(copyright_frame, text="© 速光网络软件开发  抖音号关注：dubaishun12", 
                                   font=("微软雅黑", 9), foreground="#666666")
        copyright_label.pack()
    
    def browse_source_folder(self):
        """选择源文件夹"""
        folder = filedialog.askdirectory(title="选择源文件夹")
        if folder:
            self.source_folder.set(folder)
    
    def browse_output_folder(self):
        """选择输出文件夹"""
        folder = filedialog.askdirectory(title="选择输出文件夹")
        if folder:
            self.output_folder.set(folder)
    
    def log_message(self, message: str):
        """添加日志消息"""
        self.log_text.insert(tk.END, message + "\n")
        self.log_text.see(tk.END)
        self.root.update_idletasks()
    
    def validate_inputs(self) -> bool:
        """验证输入参数"""
        if not self.source_folder.get():
            messagebox.showerror("错误", "请选择源文件夹")
            return False
        
        if not self.output_folder.get():
            messagebox.showerror("错误", "请选择输出文件夹")
            return False
        
        if not os.path.isdir(self.source_folder.get()):
            messagebox.showerror("错误", "源文件夹不存在")
            return False
        
        # 创建输出文件夹（如果不存在）
        try:
            os.makedirs(self.output_folder.get(), exist_ok=True)
        except Exception as e:
            messagebox.showerror("错误", f"无法创建输出文件夹: {e}")
            return False
        
        if self.resize_mode.get() == "width":
            try:
                width = int(self.target_width.get())
                if width <= 0:
                    raise ValueError("宽度必须大于0")
            except ValueError:
                messagebox.showerror("错误", "请输入有效的宽度值")
                return False
        
        elif self.resize_mode.get() == "filesize":
            try:
                size = float(self.target_size.get())
                if size <= 0:
                    raise ValueError("文件大小必须大于0")
            except ValueError:
                messagebox.showerror("错误", "请输入有效的文件大小")
                return False
        
        return True
    
    def start_processing(self):
        """开始处理图片"""
        if not self.validate_inputs():
            return
        
        if self.is_processing:
            return
        
        self.is_processing = True
        self.start_button.config(state="disabled")
        self.stop_button.config(state="normal")
        self.processor.stop_processing = False
        
        # 清空日志
        self.log_text.delete(1.0, tk.END)
        
        # 在后台线程中处理
        thread = threading.Thread(target=self.process_images, daemon=True)
        thread.start()
    
    def stop_processing(self):
        """停止处理"""
        self.processor.stop_processing = True
        self.log_message("正在停止处理...")
    
    def process_images(self):
        """处理图片的主函数（在后台线程中运行）"""
        try:
            # 获取所有图片文件（排除输出目录）
            self.status_label.config(text="扫描图片文件...")
            image_files = self.processor.get_image_files(self.source_folder.get(), self.output_folder.get())
            
            if not image_files:
                self.log_message("未找到支持的图片文件")
                return
            
            self.log_message(f"找到 {len(image_files)} 个图片文件")
            
            # 处理每个图片
            processed = 0
            failed = 0
            skipped = 0  # 跳过的数量
            replaced = 0  # 替换的数量
            
            for i, image_path in enumerate(image_files):
                if self.processor.stop_processing:
                    self.log_message("处理已停止")
                    break
                
                # 更新进度
                progress = (i / len(image_files)) * 100
                self.progress_var.set(progress)
                
                # 获取相对路径和文件名
                source_path = Path(self.source_folder.get())
                image_file_path = Path(image_path)
                relative_path = image_file_path.relative_to(source_path)
                
                filename = relative_path.name
                self.status_label.config(text=f"处理: {relative_path}")
                
                # 构造输出路径，保持子目录结构
                output_path = os.path.join(self.output_folder.get(), str(relative_path))
                
                # 确保输出目录存在
                output_dir = os.path.dirname(output_path)
                os.makedirs(output_dir, exist_ok=True)
                
                # 检查是否会替换文件
                will_replace = os.path.exists(output_path)
                
                # 根据模式处理图片
                success = False
                skip_reason = None
                
                if self.resize_mode.get() == "width":
                    target_width = int(self.target_width.get())
                    success = self.processor.resize_by_width(image_path, target_width, output_path)
                    
                    # 检查是否是跳过情况（通过检查原图尺寸）
                    try:
                        with Image.open(image_path) as temp_img:
                            if temp_img.size[0] <= target_width:
                                skip_reason = f"原宽度({temp_img.size[0]})小于等于目标宽度({target_width})，已复制原文件"
                                skipped += 1
                    except Exception:
                        pass
                        
                elif self.resize_mode.get() == "filesize":
                    target_size = float(self.target_size.get())
                    success = self.processor.resize_by_file_size(image_path, target_size, output_path)
                    
                    # 检查是否是跳过情况（通过检查原文件大小）
                    try:
                        original_size_mb = os.path.getsize(image_path) / (1024 * 1024)
                        if original_size_mb <= target_size:
                            skip_reason = f"原大小({original_size_mb:.2f}MB)小于等于目标大小({target_size}MB)，已复制原文件"
                            skipped += 1
                    except Exception:
                        pass
                
                if success:
                    if skip_reason:
                        self.log_message(f"○ 跳过: {filename} - {skip_reason}")
                    else:
                        processed += 1
                        if will_replace:
                            replaced += 1
                            self.log_message(f"✓ 已处理(替换): {filename}")
                        else:
                            self.log_message(f"✓ 已处理: {filename}")
                else:
                    failed += 1
                    self.log_message(f"✗ 处理失败: {filename}")
            
            # 完成处理
            self.progress_var.set(100)
            self.status_label.config(text="处理完成")
            
            # 显示统计信息
            summary = f"\n处理完成! 成功: {processed}, 跳过: {skipped}, 失败: {failed}"
            if replaced > 0:
                summary += f", 替换: {replaced}"
            summary += "  全部已输出到目标文件夹。"
            self.log_message(summary)
            
        except Exception as e:
            self.log_message(f"处理过程中出错: {e}")
        
        finally:
            # 重置UI状态
            self.is_processing = False
            self.start_button.config(state="normal")
            self.stop_button.config(state="disabled")


def main():
    """主函数"""
    root = tk.Tk()
    app = ImageResizerGUI(root)
    
    # 设置窗口图标（如果有的话）
    try:
        # root.iconbitmap('icon.ico')  # 如果有图标文件
        pass
    except:
        pass
    
    # 启动主循环
    root.mainloop()


if __name__ == "__main__":
    main()